﻿#region File and License Information
/*
<File>
	<License>
		Copyright © 2009 - 2017, Daniel Vaughan. All rights reserved.
		This file is part of Calcium (http://calciumsdk.net), 
		which is released under the MIT License.
		See file /Documentation/License.txt for details.
	</License>
	<CreationDate>2013-06-16 15:51:54Z</CreationDate>
</File>
*/
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Linq;

using Outcoder.Collections;
using Outcoder.ComponentModel;

namespace Outcoder.UI.Xaml
{
	/// <summary>
	/// This class allows you to bind to the <c>ListBox</c>
	/// SelectedItems property.
	/// </summary>
	public static class ListBoxSelectedItemsBinder
	{
		public static readonly DependencyProperty ItemsProperty
			= DependencyProperty.RegisterAttached(
				"Items",
				typeof(IList),
				typeof(ListBoxSelectedItemsBinder),
				new PropertyMetadata(null, ItemsPropertyChanged));

		public static void SetItems(ListBox listBox, IList list)
		{
			listBox.SetValue(ItemsProperty, list);
		}

		public static IList GetItems(ListBox listBox)
		{
			return (IList)listBox.GetValue(ItemsProperty);
		}

		static readonly DependencyProperty SelectedItemsBehaviorProperty
			= DependencyProperty.RegisterAttached(
				"SelectedItemsBehavior",
				typeof(SelectedItemsBehavior),
				typeof(ListBox), null);

		static void ItemsPropertyChanged(
			DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var target = d as ListBox;
			if (target != null)
			{
				GetOrCreateBehavior(target, (IList)e.NewValue);
			}
		}

		static SelectedItemsBehavior GetOrCreateBehavior(
			ListBox listBox, IList list)
		{
			var behavior = (SelectedItemsBehavior)listBox.GetValue(
												SelectedItemsBehaviorProperty);
			if (behavior == null)
			{
				behavior = new SelectedItemsBehavior(listBox, list);
				listBox.SetValue(SelectedItemsBehaviorProperty, behavior);
			}
			else
			{
				behavior.HandleSourceListChanged(list);
			}

			return behavior;
		}

		class SelectedItemsBehavior
		{
			readonly ListBox listBox;
			IList sourceList;

			public SelectedItemsBehavior(ListBox listBox, IList sourceList)
			{
				this.listBox = ArgumentValidator.AssertNotNull(listBox, "listBox");
				this.sourceList = sourceList;

				if (sourceList != null)
				{
					listBox.SelectedItems.AddRange(sourceList);
					AttachCollection(sourceList);
				}

				this.listBox.SelectionChanged += HandleSelectionChanged;
				DataContextChangedListener.Subscribe(listBox, HandleDataContextChanged);
			}

			void AttachCollection(IList list)
			{
				var collection = list as INotifyCollectionChanged;
				if (collection != null)
				{
					collection.CollectionChanged -= CollectionOnCollectionChanged;
					collection.CollectionChanged += CollectionOnCollectionChanged;
				}
			}

			void DetachCollection(IList list)
			{
				var collection = list as INotifyCollectionChanged;
				if (collection != null)
				{
					collection.CollectionChanged -= CollectionOnCollectionChanged;
				}
			}

			void CollectionOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
			{
				if (listBox == null)
				{
					return;
				}

				listBox.SelectionChanged -= HandleSelectionChanged;

				try
				{
					var collectionAction = e.Action;
					if (collectionAction == NotifyCollectionChangedAction.Reset)
					{
						RepopulateListBox();
					}
					else if (collectionAction == NotifyCollectionChangedAction.Add)
					{
						var newItems = e.NewItems;
						if (newItems != null)
						{
							var list = new List<object>();
							foreach (var newItem in newItems)
							{
								list.Add(newItem);
							}

							foreach (var selectedItem in listBox.SelectedItems)
							{
								list.Remove(selectedItem);
							}

							if (list.Count > 0)
							{
								listBox.SelectedItems.AddRange(list);
							}
						}
						else
						{
							RepopulateListBox();
						}
					}
					else if (collectionAction == NotifyCollectionChangedAction.Remove)
					{
						var newItems = e.NewItems;
						if (newItems != null)
						{
							var list = new List<object>();
							foreach (var selectedItem in listBox.SelectedItems)
							{
								if (newItems.Contains(selectedItem))
								{
									list.Add(selectedItem);
								}
							}

							if (list.Count > 0)
							{
								listBox.SelectedItems.RemoveRange(list);
							}
						}
						else
						{
							RepopulateListBox();
						}
					}
					else if (collectionAction == NotifyCollectionChangedAction.Replace)
					{
						var newItems = e.NewItems;
						var oldItems = e.OldItems;

						if (oldItems != null)
						{
							listBox.SelectedItems.ReplaceRange(oldItems, newItems);
						}
						else
						{
							RepopulateListBox();
						}
					}
				}
				finally
				{
					listBox.SelectionChanged += HandleSelectionChanged;
				}
			}

			void RepopulateListBox()
			{
				listBox.SelectedItems.Clear();
				if (sourceList != null)
				{
					listBox.SelectedItems.AddRange(sourceList);
				}
			}

			internal void HandleSourceListChanged(IList newList)
			{
				if (sourceList != null)
				{
					DetachCollection(sourceList);
				}

				sourceList = newList;
				
				//if (sourceList != null)
				//{
				//	var itemsToRemove = new List<object>();
				//	foreach (var selectedItem in listBox.SelectedItems)
				//	{
				//		if (!sourceList.Contains(selectedItem))
				//		{
				//			itemsToRemove.Add(selectedItem);
				//		}
				//	}
				//	listBox.SelectedItems.RemoveRange(itemsToRemove);
				//
				//	listBox.SelectedItems.AddRange(sourceList);
				//}
				//else
				//{
				//	listBox.SelectedItems.Clear();
				//}

				listBox.SelectionChanged -= HandleSelectionChanged;
				try
				{
					listBox.SelectedItems.Clear();
					listBox.SelectedItems.AddRange(sourceList);
				}
				finally
				{
					listBox.SelectionChanged += HandleSelectionChanged;
				}
			}

			public void Detach()
			{
				if (listBox != null)
				{
					listBox.SelectionChanged -= HandleSelectionChanged;
					DataContextChangedListener.Unsubscribe(listBox, HandleDataContextChanged);
				}
			}

			void HandleDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
			{
				//RefreshList();
			}

			void HandleSelectionChanged(object sender, SelectionChangedEventArgs e)
			{
				if (sourceList == null)
				{
					return;
				}
				var removedItems = e.RemovedItems;
				if (removedItems != null && removedItems.Count > 0)
				{
					DetachCollection(sourceList);
					try
					{
						sourceList.RemoveRange(removedItems);
					}
					finally
					{
						AttachCollection(sourceList);
					}
				}

				var addedItems = e.AddedItems;
				if (addedItems != null && addedItems.Count > 0)
				{
					DetachCollection(sourceList);
					try
					{
						sourceList.AddRange(addedItems);
					}
					finally
					{
						AttachCollection(sourceList);
					}
				}
			}

			void RefreshList()
			{
				sourceList.Clear();

				foreach (object item in listBox.SelectedItems)
				{
					sourceList.Add(item);
				}
			}
		}
	}
}